Activité des paquets R sur GitHub

Chargement des données

Nous avons collecté pas mal de données à propos des paquets R présents sur GitHub. En particulier, nous avons le flux d'activité de tous ces paquets (avec notamment les PushEvents) ainsi que des fichiers .csv contenant les fichiers DESCRIPTION de chaque package. Ces fichiers ont été collectés à la fois sur CRAN, sur BioConductor et sur GitHub.

Il serait intéressant de comparer l'activité de ces différents groupes de paquets.

Pour ce notebook, les fichiers suivants vont être utilisés :

  • bioconductorsvn_description.csv : contient les informations de DESCRIPTION des packages présents sur BioConductor.
  • cran-description.csv : idem mais pour CRAN.
  • rforge_description.csv : idem mais pour RForge.
  • r-forge_description.csv : idem pour R-Forge.
  • github-description.csv : idem mais pour tous les packages sur GitHub.
  • github_R_pushevents.csv : contient les événements de type PushEvent relatifs aux dépôts contenant un package R sur GitHub.

In [1]:
import pandas

bioconductor = pandas.read_csv('../data/bioconductorsvn_description.csv')
cran = pandas.read_csv('../data/cran-description.csv')
rforge = pandas.read_csv('../data/rforge_description.csv')
r_forge = pandas.read_csv('../data/r-forge_description.csv')
github = pandas.read_csv('../data/github-description.csv').query('owner != "rpkg" and owner != "cran"')
events = pandas.read_csv('../data/github_R_pushevents.csv')

Le format de chacun de ces dataframes est un peu différent. cran et github nécessitent d'être pivotés, alors que events contient des données d'une toute autre nature :


In [37]:
events.head()


Out[37]:
created_at owner repository package actor
0 2013-01-01 14:01:09 SachaEpskamp qgraph qgraph SachaEpskamp
1 2013-01-01 14:38:59 trinker qdap qdap trinker
2 2013-01-01 14:48:33 trinker qdap qdap trinker
3 2013-01-01 14:58:52 trinker qdap qdap trinker
4 2013-01-01 17:14:05 trinker qdap qdap trinker

Mise en place des DataFrames

Ceux qui nécessitent juste un renommage sur le label des packages.


In [38]:
bioconductor = bioconductor.rename(columns={'Unnamed: 0': 'Pkg'}).set_index('Package', verify_integrity=True)
rforge = rforge.rename(columns={'Unnamed: 0': 'Pkg'}).set_index('Package')
r_forge = r_forge.rename(columns={'Unnamed: 0': 'Pkg'}).set_index('Package')

cran doit être pivoté et nécessite également un renommage et un index sur le nom des packages. Le dataframe cran contient également des informations pour plusieurs versions d'un même package. Par convention, nous ne garderons que la dernière information (i.e. la dernière version).


In [2]:
cran = cran.rename(columns={'package': 'Package'}).drop(labels=['version'], axis=1).drop_duplicates(['Package', 'key'], take_last=True).pivot(index='Package', columns='key', values='value')

Le dataframe github est un peu différent, dans le sens où l'identification se fait via le couple (owner, repository) et non pas via le nom du package. La première chose à faire est d'associer un nom de package à chaque dépôt. Ensuite, nous utiliserons (owner, repository) comme index.


In [40]:
_ = github[github['key'] == 'Package'][['owner', 'repository', 'value']].rename(columns={'value': 'Package'})
_ = github.merge(_, on=('owner', 'repository')).drop_duplicates(['owner', 'repository', 'key'], take_last=True)
_['url'] = _['owner'] + '/' + _['repository']
github = _.pivot(index='url', columns='key', values='value')

Enfin, le dataframe events sera utilisé ultérieurement afin de joindre les informations des autres dataframes. La colonne package va être renommée afin de faciliter la jointure avec bioconductor et cran, alors qu'une nouvelle colonne url va être construite afin de faciliter la jointure avec github. Finalement, ce dataframe va être indexé sur base de la date de l'événement, renommée en date.


In [41]:
_ = events.rename(columns={'package': 'Package', 'created_at': 'Date'})
_['url'] = _['owner'] + '/' + _['repository']
_['Date'] = pandas.to_datetime(_['Date'])
events = _.set_index('Date')

Nous savons par expérience qu'un pic d'activité inhabituel est observé en avril 2014. En réalité, ce pic correspond à une activité particulière liée aux dépôts appartenant à cran et rpkg sur GitHub. Ces deux owners servent en réalité à mirrorer les paquets qui sont présents sur CRAN, et ne représentent pas un développement de paquet ou quoi que ce soit réellement lié au développement de paquets. Nous allons donc extraire ces informations du flux d'événements.


In [42]:
events = events.query('owner != "rpkg" and owner != "cran"')

Identification de l'appartenance de chaque événement à une ou plusieurs communautés

Chaque événement est associé à un dépôt GitHub ainsi qu'à un package.

Une remarque importante : l'association entre un événement et un package provient d'un pré-traitement en amont, dont le résultat a été stocké dans le .csv à la base de events. Si, lors d'une reproduction de l'expérience courante, l'utilisateur souhaite repartir des données brutes (issues de MongoDB ou de GitHubArchive), il convient de faire une jointure avec le dataframe github, sur base de url afin d'associer un package au dépôt concerné par l'événement.

Grâce à cette association, il nous est possible d'identifier quels sont les packages qui appartiennent à telle ou telle communauté (ex. : CRAN, BioConductor ou GitHub exclusivement) et de mesurer les différences d'activité en fonction de ces communautés. La première étape consiste donc à identifier, pour chaque événement, si le dépôt relatif est lié à une (ou plusieurs) communautés.


In [43]:
# Tags data in each related data frame
cran['cran'] = 1
bioconductor['bioconductor'] = 1
rforge['rforge'] = 1
r_forge['r-forge'] = 1
events['all'] = 1

In [44]:
_ = events.merge(cran[['cran']], how='left', left_on='Package', right_index=True)
_ = _.merge(bioconductor[['bioconductor']], how='left', left_on='Package', right_index=True)
_ = _.merge(rforge[['rforge']], how='left', left_on='Package', right_index=True)
_ = _.merge(r_forge[['r-forge']], how='left', left_on='Package', right_index=True)
events = _.fillna(value=0)

In [45]:
# Tag events that are related only to github
events['github only'] = events.apply(lambda x: 1 - max(x['cran'], x['bioconductor'], x['rforge']), axis=1)

In [46]:
%matplotlib inline
columns = ['cran', 'bioconductor', 'rforge', 'r-forge', 'github only', 'all']
_ = pandas.stats.moments.rolling_sum(events[columns], 30, freq='1D', how='sum')
_.plot(figsize=(15, 6), title=u'# événements "PushEvent" par type de packages')


Out[46]:
<matplotlib.axes.AxesSubplot at 0x7fa5e5227090>

Nous pouvons également nous intéresser à la création de dépôts pour chaque catégorie de packages. En nous basant sur les PushEvent, nous pouvons supposer que la création du dépôt correspond au premier PushEvent pour chaque package.

Notons que le fait d'utiliser le Package et non (owner, repository) nous permet de filtrer les forks.


In [47]:
events_first = events.drop_duplicates(('Package',))
_1 = pandas.stats.moments.rolling_sum(events_first[columns], 4, freq='1W', how='sum')
_1.plot(figsize=(15, 6), title=u'# de création de dépôts par type de packages')


Out[47]:
<matplotlib.axes.AxesSubplot at 0x7fa5e53c1a90>

Afin de se faire une meilleure idée du comportement entre la communauté CRAN et la communauté GitHubOnly, nous pouvons représenter le ratio entre le nombre d'événements de chaque communauté.


In [48]:
_1['First events ratio'] = _1['github only'] / _1['cran']
ax = _1[['First events ratio']].plot(figsize=(15, 6))
_2 = pandas.stats.moments.rolling_sum(events[columns], 30, freq='1D', how='sum')
_2['All events ratio'] = _2['github only'] / _2['cran']
_2[['All events ratio']].plot(figsize=(15, 6), ax=ax, title=u'Ratio entre GitHub et CRAN')


Out[48]:
<matplotlib.axes.AxesSubplot at 0x7fa5e5260990>

Taille des communautés


In [49]:
github_set = set(github['Package'])
cran_set = set(cran.index)
r_forge_set = set(r_forge.index)
rforge_set = set(rforge.index)
bioconductor_set = set(bioconductor.index)

In [51]:
from matplotlib_venn import venn2, venn3
from matplotlib import pyplot as plt

figure, axes = plt.subplots(1, 2, figsize=(15,6))

venn3((github_set, cran_set, bioconductor_set), ('github', 'cran', 'bioconductor'), ax=axes[0])
venn3((github_set, cran_set, r_forge_set), ('github', 'cran', 'r-forge'), ax=axes[1])


Out[51]:
<matplotlib_venn._common.VennDiagram instance at 0x7fa5e63f4b90>